package pl.polidea.imf.blackberry.service;

import org.json.me.JSONObject;

import microlog.net.sf.microlog.core.Logger;
import microlog.net.sf.microlog.core.LoggerFactory;
import net.rim.device.api.system.Application;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.system.JPEGEncodedImage;
import net.rim.device.api.system.KeyListener;
import net.rim.device.api.ui.Keypad;
import net.rim.device.api.ui.component.Dialog;
import pl.polidea.imf.blackberry.service.statemachine.State;
import pl.polidea.imf.blackberry.service.statemachine.StateInit;
import pl.polidea.imf.blackberry.service.statemachine.StateLogin;
import pl.polidea.imf.blackberry.service.statemachine.StateOffLine;
import pl.polidea.imf.blackberry.service.statemachine.StateRegistration;
import pl.polidea.imf.blackberry.ui.GlobalUIHelper;
import pl.polidea.imf.blackberry.ui.UiController;
import pl.polidea.imf.blackberry.util.DeviceStatusHelper;
import pl.polidea.imf.blackberry.util.PhoneStatusChangeChecker;
import pl.polidea.imf.blackberry.util.Settings;
import pl.polidea.imf.blackberry.util.SystemLogHelper;
import pl.polidea.imf.blackberry.util.PhoneStatusChangeChecker.PhoneStatusChangeListener;
import pl.polidea.imf.common.java.http.HttpConnectionFactory;
import pl.polidea.imf.common.java.http.requests.ServerCommunicationError;
import pl.polidea.imf.common.java.impl.http.HttpConnectionFactoryImpl;
import pl.polidea.imf.common.java.impl.util.ApplicationInfoProviderImpl;
import pl.polidea.imf.common.java.issues.IssueBuilder;
import pl.polidea.imf.common.java.log.LogBuilder;
import pl.polidea.imf.common.java.model.AssertData;
import pl.polidea.imf.common.java.model.IssueItem;
import pl.polidea.imf.common.java.model.LogItem;
import pl.polidea.imf.common.java.model.LogoutReq;
import pl.polidea.imf.common.java.model.RegistrationReq;
import pl.polidea.imf.common.java.model.RegistrationResp;
import pl.polidea.imf.common.java.model.Session;
import pl.polidea.imf.common.java.net.CommunicationManager;
import pl.polidea.imf.common.java.util.ApplicationInfoProvider;
import pl.polidea.imf.common.java.util.Base64Coder;
import pl.polidea.imf.common.java.util.CryptoWrapper;

public final class IMFService {

	private static final Logger logger = LoggerFactory.getLogger(IMFService.class);
	private final UiController uiController = new UiController();
	private static IMFService service;

	private final CommunicationManager communicationManager;
	private final IMFEngine engine;

	private final  Settings settings = Settings.getInstance();

	private State currentState;
	private State previousState;

	private volatile boolean isRunning;
	private final Object lockObject = new Object();

	private boolean stateChangeRequested;

	private boolean stateChanged;
	private final Thread loopThread;

	private int issueKeyPressed = 0;
	private int reregisterKeyPressed = 0;

	private static final int ISSUE_RAPORT_KEY = Keypad.KEY_VOLUME_DOWN;
	private static final int STATUS_SCREEN_KEY = Keypad.KEY_VOLUME_UP;

	private static final int KEY_PRESSED_LIMIT = 5;

	private Bitmap lastScreenShot;

	private Session session = Session.DEFAULT_SESSION;
	
	PhoneStatusChangeChecker phoneStatusChangeChecker;
	
	static{
		HttpConnectionFactory.registerPrototype(new HttpConnectionFactoryImpl());
		ApplicationInfoProvider.registerProvider(new ApplicationInfoProviderImpl());
	}

	private IMFService() {
		
		DeviceStatusHelper.checkPermissionsAndAsk();
		communicationManager = new CommunicationManager();
		engine = new IMFEngine(communicationManager);
		isRunning = true;
		stateChangeRequested = false;
		stateChanged = true;
		loopThread = new Thread() {
			public void run() {
				serviceLoop();
			}
		};
		loopThread.start();
		Application.getApplication().addKeyListener(issueRaportKeyListener);
		phoneStatusChangeChecker = new PhoneStatusChangeChecker();
		phoneStatusChangeChecker.registerListener(new PhoneStatusChangeListener() {
			
			public void statusChanged(JSONObject state) {
				engine.collectCondition(state);
				
			}
		});
		changeState(new StateInit(StateInit.SUB_STATE_CHECK_IF_REGISTERED));
	}

	public static void collectLog(LogItem logItem) {
		if(service==null){
			return;
		}
		getInstance().collectLog_(logItem);
	}

	public static void reportIssue(IssueItem issueItem) {
		if(service==null){
			return;
		}
		getInstance().reportIssue_(issueItem);
	}

	public static void imfAssert(boolean condidition, int assertId, String msg) {
		if(service==null){
			return;
		}
		getInstance().imfAssert_(condidition, assertId, msg);
	}
	
	public static void start() {
		getInstance().start_();
	
	}
	
	public static void stop() {
		if(service==null){
			return;
		}
		Settings.getInstance().setField(Settings.KEY_IS_CRASH, Boolean.FALSE);
		Settings.getInstance().commit();
		getInstance().stop_();
	}
	
	public void cleanStorage(){
		engine.cleanStorage();
	}
		

	private void imfAssert_(boolean condidition, int assertId, String msg) {
		if (!session.isLogAsserts()) {
			return;
		}
		if (!condidition) {
			try {
				throw new IllegalStateException("Assert failed!");
			} catch (Throwable t) {
				t.printStackTrace();
			}
			engine.collectIssue(new IssueBuilder(IssueItem.TYPE.ASSERT, msg, ApplicationInfoProvider.getRevision(), System.currentTimeMillis()).setAssertData(new AssertData(assertId, 0, null, null)).build());

		}
	}

	private void start_() {
		Settings settings = Settings.getInstance();

		if (settings.getFieldBool(Settings.KEY_IS_CRASH)) {
			if (session.isLogCrashes()) {
				reportIssue(new IssueBuilder(IssueItem.TYPE.CRASH, "CRASH HAS BEEN DTECTED", ApplicationInfoProvider.getRevision(), System.currentTimeMillis()).build());
			}
		}

		Settings.getInstance().setField(Settings.KEY_IS_CRASH, Boolean.TRUE);
		Settings.getInstance().commit();
	};

	

	private synchronized void stop_() {
		engine.stopNetworkWorker();
		Application.getApplication().removeKeyListener(issueRaportKeyListener);
		isRunning = false;
		try {
			communicationManager.logout(new LogoutReq(session.getSessionKey(), System.currentTimeMillis()));
		} catch (ServerCommunicationError e) {
			System.out.println("Cannot logout...");
		}
		service = null;

	}

	private void collectLog_(LogItem logItem) {
		if (logItem.getType() == LogItem.TYPE.DEBUG && !session.isLogDebugMsg()) {
			return;
		}
		if (logItem.getType() == LogItem.TYPE.METHOD && !session.isLogMethods()) {
			return;
		}

		try {
			engine.collectLog(logItem);
		} catch (Throwable t) {
			System.out.println(t.toString() + t.getMessage());
		}
	}

	private void reportIssue_(IssueItem issueItem) {
		engine.collectIssue(issueItem);
	}

	private void serviceLoop() {
		State nextState;
		while (isRunning) {
			synchronized (this) {

				if (stateChangeRequested) {
					stateChangeRequested = false;
					nextState = currentState.doWork();
					stateChanged = true;
					if (nextState != null) {
						changeState(nextState);
					}
				} else {
					//
				}
				if (stateChangeRequested) {
					continue;
				}
			}

			synchronized (lockObject) {
				try {
					
					lockObject.wait();
				} catch (InterruptedException e) {

					e.printStackTrace();
				}
			}

		}
	}

	private synchronized void changeState(State state) {
		// for (int i = 0; i < 1000; i++) {
		// collectLog_(new LogBuilder(LogItem.LEVEL.WARNING, LogItem.TYPE.DEBUG,
		// "Przykladow log", "12d", System.currentTimeMillis()).build());
		// }
		if (!stateChanged) {
			throw new IllegalStateException("Cannot change state: previous state change is not completed!");
		}

		stateChangeRequested = true;
		stateChanged = false;

		previousState = currentState;
		currentState = state;
		logger.debug("Changing state from: " + previousState + "to: " + currentState);
		synchronized (lockObject) {
			lockObject.notifyAll();
		}

	}

	public void uiRequestRegistration(String email, String pass) {
		try {
			RegistrationResp resp = communicationManager.register(new RegistrationReq(email, CryptoWrapper.getMD5(pass), "deviceId", "12321", "appVer", System.currentTimeMillis()));
			settings.setField(Settings.KEY_REG_KEY, resp.getRegKey());
			settings.commit();
			changeState(new StateRegistration(StateRegistration.SUB_STATE_REGISTRATION_OK));
		} catch (ServerCommunicationError e) {
			logger.debug("Cannot register: " + e.toString());
			changeState(new StateRegistration(StateRegistration.SUB_STATE_REGISTRATION_FAILED));
		}
	}

	public void uiRequestReRegistration() {
		changeState(new StateRegistration(StateRegistration.SUB_STATE_REGISTRATION_GET_USER_DATA));
	}

	public void uiRequestedIssueReport(String message) {
		JPEGEncodedImage encoder = JPEGEncodedImage.encode(lastScreenShot, 50);

		char[] screenShotB64 = Base64Coder.encode(encoder.getData());
		String screenShotB64String = new String(screenShotB64);
		IssueItem issue = new IssueBuilder(IssueItem.TYPE.MANUAL, message, "123", System.currentTimeMillis()).setScreenshot(screenShotB64String).build();
		engine.collectIssue(issue);
	}

	public void uiRequestedCollectSystemLog() {
		new Thread() {
			public void run() {
				String log = SystemLogHelper.getSystemLogs();
				collectLog_(new LogBuilder(LogItem.LEVEL.WARNING, LogItem.TYPE.DEBUG, log, ApplicationInfoProvider.getRevision(), System.currentTimeMillis()).build());
				Dialog dialog = new Dialog(Dialog.D_OK, "Collecting system log complete", Dialog.OK, Bitmap.getPredefinedBitmap(Bitmap.EXCLAMATION), Dialog.VERTICAL_SCROLL);
				GlobalUIHelper.pushGlobalScreen(dialog);
			}
		}.start();
	}

	public void uiRequestRegistrationCanceled() {
		changeState(new StateOffLine(StateOffLine.SUB_STATE_OFFLINE));
	}

	public CommunicationManager getCommunicationManager() {
		return communicationManager;
	}

	public synchronized static IMFService getInstance() {
		if (service == null) {
			service = new IMFService();
		}
		return service;
	}

	public synchronized void setSession(Session session) {
		this.session = session;
	}

	public String getSessionKey() {
		return session.getSessionKey();
	}

	public synchronized Session getSession() {
		return session;
	}

	public void startNetworkWorker() {
		engine.startNetworkWorker();
	}

	public void stopNetworkWorker() {
		engine.stopNetworkWorker();
	}

	public void logout() {
		changeState(new StateLogin(StateLogin.SUB_STATE_LOGIN_LOGOUT));
	}

	public UiController getUiController() {
		return uiController;
	}

	private KeyListener issueRaportKeyListener = new KeyListener() {

		public boolean keyChar(char key, int status, int time) {
			return false;
		}

		public boolean keyDown(int keycode, int time) {
			return false;
		}

		public boolean keyRepeat(int keycode, int time) {
			if (Keypad.key(keycode) == ISSUE_RAPORT_KEY) {
				issueKeyPressed++;
				if (issueKeyPressed > KEY_PRESSED_LIMIT) {
					lastScreenShot = new Bitmap(Display.getWidth(), Display.getHeight());
					Display.screenshot(lastScreenShot);
					uiController.showIssueReportPopUp();
					issueKeyPressed = 0;
				}
				return true;
			} else if (Keypad.key(keycode) == STATUS_SCREEN_KEY) {
				reregisterKeyPressed++;
				if (reregisterKeyPressed > KEY_PRESSED_LIMIT) {
					uiController.showStatusScreen(currentState);
					reregisterKeyPressed = 0;
				}
				return true;
			}
			return false;
		}

		public boolean keyStatus(int keycode, int time) {
			return false;
		}

		public boolean keyUp(int keycode, int time) {
			return false;
		}

	};

}
